=begin

This file is part of the arena-ruby-sandbox project, http://arena-ruby-sandbox.googlecode.com

Copyright (c) 2009-2009 Roberto Dario Seminara <robertodarioseminara@gmail.com>

arena-ruby-sandbox is free software: you can redistribute it and/or modify
it under the terms of the gnu general public license as published by
the free software foundation, either version 3 of the license, or
(at your option) any later version.

arena-ruby-sandbox is distributed in the hope that it will be useful,
but without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.  see the
gnu general public license for more details.

you should have received a copy of the gnu general public license
along with arena-ruby-sandbox.  if not, see <http://www.gnu.org/licenses/>.
=end

require "context_switcher.rb"
require "reverse_context_switcher.rb"
require "holder.rb"
require "introspection_disable.rb"

module RubySandbox
class Sandbox

	attr_accessor	:permitted_methods
	attr_accessor	:denied_object_methods
	attr_accessor	:permitted_constants
	attr_accessor	:service_provider



	def initialize

		@permitted_methods = []
#		@permitted_constants = ["ThreadError", "Numeric", "RUBY_DESCRIPTION", "String", "IRB", "Bignum", "Proc", "IndexError", "Process", "SecurityError", "Time", "FileTest", "MatchData", "Exception", "Method", "NoMethodError", "FALSE", "Object", "StopIteration", "ARGF", "TOPLEVEL_BINDING", "RUBY_PLATFORM", "Integer", "TypeError", "ZeroDivisionError", "SystemExit", "UnboundMethod", "NotImplementedError", "Kconv", "Kernel", "STDERR", "RELEASE_DATE", "TRUE", "RUBY_VERSION", "Exception2MessageMapper", "Interrupt", "SyntaxError", "Enumerable", "Class", "Continuation", "EOFError", "RangeError", "Data", "Thread", "GC", "STDIN", "RUBY_COPYRIGHT", "Array", "File", "MatchingData", "LocalJumpError", "Binding", "NoMemoryError", "Marshal", "ENV", "NIL", "Comparable", "RubyToken", "ObjectSpace", "FloatDomainError", "RUBY_PATCHLEVEL", "Regexp", "Float", "SystemStackError", "ArgumentError", "RuntimeError", "Precision", "ThreadGroup", "SLex", "Range", "Fixnum", "FalseClass", "IO", "PLATFORM", "Errno", "StandardError", "Dir", "RUBY_RELEASE_DATE", "ARGV", "RubyLex", "RegexpError", "Hash", "LoadError", "Signal", "TrueClass", "NameError", "NilClass", "STDOUT", "VERSION", "SystemCallError", "SignalException", "Math", "Readline", "ScriptError", "IOError", "Symbol", "Struct", "Module"]
		@permitted_constants = [ "Exception", "Time", "Object", "SystemExit", "NoMethodError", "IRB", "String", "STDIN"]
		
		
		@permitted_constants = @permitted_constants + ["RubyLex","Symbol","Array","Hash", "Interrupt","SignalException", "Kconv"]
		
		@denied_object_methods = ["methods", "public_methods", "instance_eval"]

		@constants = Hash.new
		@method_code = Hash.new
		
	end

	def permit( *objs )
		objs.each do |obj|
		
			if obj.instance_of? Symbol or obj.instance_of? String
				permitted_methods << obj.to_sym				

			elsif obj.instance_of? Class
				permit( *( obj.instance_methods - obj.superclass.instance_methods ) )
			end
		end
	end	

	def run(code)
		holder = create_holder(code)

		unprivileged_block do
			holder.run_code
		end

	end

	def create_holder(code)
		context_switcher = RubySandbox::ContextSwitcher.new(self, service_provider, @permitted_methods)
		holder = Holder.new(context_switcher, code)

		holder
	end

	def permit_method?(m)
		permitted_methods.include?(m)
	end

	def privileged_block
		enable_privileges
		begin
			yield
		ensure
			disable_privileges
		end
	end


	def unprivileged_block
		disable_privileges
		begin
			yield
		ensure
			enable_privileges
		end
	end
	
	def deactivate_methods

		@secret_singleton_method_added = "_" + rand(10000000000000000000000000000000).to_s

		eval("
		class ::Object
			alias :#{@secret_singleton_method_added} :singleton_method_added
			def singleton_method_added(m)
				disable_privileges
			end
		end
		")
		
		denied_object_methods.each do |m|
			@method_code[m] = "_" + rand(10000000000000000000000000000000).to_s

			eval("
			class ::Object
				alias :#{@method_code[m]} :#{m}
				undef :#{m}
			end
			")
		end
		
	end
	
	def activate_methods

		eval("
		class ::Object
			undef :singleton_method_added
			alias :singleton_method_added :#{@secret_singleton_method_added} 
			undef :#{@secret_singleton_method_added}
		end
		")

		denied_object_methods.each do |m|
			eval("
			class ::Object
				alias :#{m} :#{@method_code[m]} 
				undef :#{@method_code[m]}
			end
			")
		end
	end


	def enable_privileges
		
		# activar instance eval
		
		activate_methods
		
		@constants.each do |key,value|
			#Object.instance_eval{ eval("::#{key} = value") }
			Object.set_constant(key,value)
		end
				
	end
	def disable_privileges
		
		# desactivar instance eval
		deactivate_methods
		
		# para desactivar los privilegios, se desactivan todas las constantes
		# a excepcion de las constantes permitidas
		(Object.constants - permitted_constants).select{|x| not x =~ /::/}.each do |c|
			@constants[c] = eval(c);
#			Object.instance_eval{ remove_const(c.to_sym) }
			Object.delete_constant(c)

		end
	end
	
	def wrap_out( obj )
		return obj.dup if obj.instance_of? String
		ReverseContextSwitcher.new(self,obj)
	end
	
	def wrap_in( obj )
		return obj.dup if obj.instance_of? String
		ContextSwitcher.new(self,obj, obj.methods)
	end

end
end

